/*
 * BlackLightning.FrontEnd
 * Andy Tidball
 * 
 * Project: Black Lightning
 * Copyright: GNU General Public License
 */

using System;
using System.Collections.Generic;
using System.Diagnostics;
using BlackLightning.PluginLib.LoginClients;
using BlackLightning.PluginLib.Modules;
using BLLib.Net;

namespace BlackLightning {
	/// <summary>
	/// Encapsulates a connection to the front end.
	/// </summary>
	/// <typeparam name="TPacket">The type of packet sent and received by the front end.</typeparam>
	internal abstract class FrontEnd : PacketConnection {
		// static member data
		private static Dictionary<string, List<int>> _ClaimedProcessIDs; // maps exe names to lists of process IDs which are already claimed

		/// <summary>
		/// Static constructor.
		/// </summary>
		static FrontEnd() {
			_ClaimedProcessIDs = new Dictionary<string,List<int>>();
		}

		/// <summary>
		/// Creates a derived instance of FrontEnd appropriate for the given SAL file.
		/// </summary>
		/// <param name="SAL">An SalFile as returned by the login server.</param>
		/// <returns>A new instance of a type of FrontEnd appropriate for the SalFile.</returns>
		public static FrontEnd Create(SalFile SAL) {
			FrontEnd FE = null;
			switch (SAL.ClientCode) {
				case "STORM": FE = new StormFront(); break;
				case "WIZ":   FE = new Wizard();     break;
				default:      throw new InvalidOperationException("Unknown client code in given SAL file.");
			}
			FE.Initialize(SAL);
			return FE;
		}

		/// <summary>
		/// Finds the running process matching the given exe name which is not already in the claimed list.
		/// </summary>
		/// <returns>The process matching the given exe name which is not already in the claimed list.</returns>
		private static Process ClaimFreeProcess(string ExeName) {
			// grab a list of processes with the given name
			Process[] Processes  = Process.GetProcessesByName(ExeName.Replace(".exe",""));

			// either grab a list of already claimed IDs or create a new empty one if there aren't any yet
			List<int> ProcessIDs;
			try {
				ProcessIDs = _ClaimedProcessIDs[ExeName];
			} catch (KeyNotFoundException) {
				ProcessIDs = new List<int>();
				_ClaimedProcessIDs[ExeName] = ProcessIDs;
			}

			// run through each process we found
			foreach (Process P in Processes) {
				// check if this process ID is already claimed
				if (!ProcessIDs.Contains(P.Id)) {
					// this is the process that hasn't been claimed yet
					ProcessIDs.Add(P.Id);
					return P;
				}
			}

			// unable to find a free process, this should never happen
			throw new InvalidOperationException("Unable to find a free process with name: " + ExeName);
		}

		// member data
		private SalFile    _SalFile        = null; // the SAL file used to launch the front end
		private string     _Identification = null; // the code sent to identify the front end to the game server
		private Process    _Process        = null; // the process of the front end program

		/// <summary>
		/// Creates a new FrontEnd.
		/// </summary>
		/// <param name="PacketTerminator">The string which marks the end of a packet.</param>
		/// <param name="AppendTerminator">Whether or not to append the packet terminator to outgoing packets that don't already end with it.</param>
		/// <param name="SendNewLine">The string that the other end of the connection recognizes as a new line when we're sending to it.</param>
		/// <param name="ReceiveNewLine">The string that the other end of the connection recognizes as a new line when we're receiving from it.</param>
		public FrontEnd(string PacketTerminator, bool AppendTerminator, string SendNewLine, string ReceiveNewLine)
			: base(PacketTerminator, AppendTerminator, SendNewLine, ReceiveNewLine) {
		}

		/// <summary>
		/// Initializes the front end with an SAL file.
		/// </summary>
		/// <param name="SAL">The SAL file to initialize the front end with, as received from the login server.</param>
		public void Initialize(SalFile SAL) {
			_SalFile = new SalFile(SAL);
			_SalFile.GameHost = "localhost";
			_SalFile.GamePort -= 1;
		}

		/// <summary>
		/// Executes the front end program and connects to it via localhost.  Blocks until connection complete.
		/// </summary>
		/// <param name="LauncherPath">The full path and filename of the Simutronics Launcher executable.</param>
		public void ExecuteAndConnect(string LauncherPath) {
			// create a new listening connection on the port we chose
			Connection Listener = new Connection();
			Listener.Blocking = true;
			Listener.Listen(_SalFile.GamePort, 1);

			// save the SAL file somewhere
			string Filename = _SalFile.Save(BlackLightning.PreferencesPath);

			// execute the launcher
			Process.Start(LauncherPath, Filename);

			// accept the front end's connection
			base.Connection = Listener.Accept();
			base.Connection.Blocking = true;

			// claim the front end's process
			_Process = ClaimFreeProcess(ExecutableFilename);

			// close the listening connection
			Listener.Disconnect();
		}

		/// <summary>
		/// Disconnects from the front end program and causes it to exit.
		/// </summary>
		public void Exit() {
			// close the front end's process, which should cause it to disconnect from us
			_Process.CloseMainWindow();
		}

		/// <summary>
		/// Brings the main window of the front end application to the foreground.
		/// </summary>
		public void BringToFront() {
			BlackLightning.SetForegroundWindow(_Process.MainWindowHandle);
		}

		/// <summary>
		/// Gets or sets the identification string sent by the front end to identify itself to the game server.
		/// </summary>
		protected string Identification {
			get {
				return _Identification;
			}
			set {
				_Identification = value;
			}
		}

		/// <summary>
		/// Gets the SalFile that the FrontEnd was Initialized with.  Mainly useful for the Key during Handshaking.
		/// </summary>
		protected SalFile SalFile {
			get {
				return _SalFile;
			}
		}

		/// <summary>
		/// Gets the filename of the derived front end's executable (no path information).
		/// </summary>
		public abstract string ExecutableFilename { get; }

		/// <summary>
		/// Handshakes with the given already-connected connection to a game server.
		/// </summary>
		/// <param name="C">A connection which has just connected to the game server.</param>
		public abstract void Handshake(Connection C, bool FirstConnection);
	}
}
